/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xalan.processor;

import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.NamespaceSupport;

import java.util.Stack;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.TemplatesHandler;

import wx.xml.xalan.xalan.extensions.ExpressionVisitor;
import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xalan.res.XSLTErrorResources;
import wx.xml.xalan.xalan.templates.Constants;
import wx.xml.xalan.xalan.templates.ElemForEach;
import wx.xml.xalan.xalan.templates.ElemTemplateElement;
import wx.xml.xalan.xalan.templates.Stylesheet;
import wx.xml.xalan.xalan.templates.StylesheetRoot;
import wx.xml.xalan.xml.utils.BoolStack;
import wx.xml.xalan.xml.utils.NamespaceSupport2;
import wx.xml.xalan.xml.utils.NodeConsumer;
import wx.xml.xalan.xml.utils.PrefixResolver;
import wx.xml.xalan.xml.utils.SAXSourceLocator;
import wx.xml.xalan.xml.utils.XMLCharacterRecognizer;
import wx.xml.xalan.xpath.XPath;
import wx.xml.xalan.xpath.compiler.FunctionTable;

/**
 * Initializes and processes a stylesheet via SAX events.
 * This class acts as essentially a state machine, maintaining
 * a ContentHandler stack, and pushing appropriate content
 * handlers as parse events occur.
 *
 * @xsl.usage advanced
 */
public class StylesheetHandler extends DefaultHandler
    implements TemplatesHandler, PrefixResolver, NodeConsumer {


    /**
     * If getStylesheetType returns this value, the current stylesheet
     * is a root stylesheet.
     *
     * @xsl.usage internal
     */
    public static final int STYPE_ROOT = 1;
    /**
     * If getStylesheetType returns this value, the current stylesheet
     * is an included stylesheet.
     *
     * @xsl.usage internal
     */
    public static final int STYPE_INCLUDE = 2;
    /**
     * If getStylesheetType returns this value, the current stylesheet
     * is an imported stylesheet.
     *
     * @xsl.usage internal
     */
    public static final int STYPE_IMPORT = 3;
    /**
     * The root stylesheet of the stylesheets tree.
     */
    StylesheetRoot m_stylesheetRoot;
    /**
     * The last stylesheet that was popped off the stylesheets stack.
     */
    Stylesheet m_lastPoppedStylesheet;
    /**
     * This will act as a stack to keep track of the
     * current include base.
     */
    Stack m_baseIdentifiers = new Stack();
    /**
     * Stack of NamespaceSupport objects.
     */
    Stack m_nsSupportStack = new Stack();
    /**
     * The function table of XPath and XSLT;
     */
    private FunctionTable m_funcTable = new FunctionTable();
    /**
     * The flag for the setting of the optimize feature;
     */
    private boolean m_optimize = true;
    /**
     * The flag for the setting of the incremental feature;
     */
    private boolean m_incremental = false;
    /**
     * The flag for the setting of the source_location feature;
     */
    private boolean m_source_location = false;

    ////////////////////////////////////////////////////////////////////
    // Implementation of the TRAX TemplatesBuilder interface.
    ////////////////////////////////////////////////////////////////////
    /**
     * The level of the stylesheet we are at.
     */
    private int m_stylesheetLevel = -1;
    /**
     * m_parsingComplete becomes true when the top-level stylesheet and all
     * its included/imported stylesheets have been been fully parsed, as an
     * indication that composition/optimization/compilation can begin.
     *
     * @see isStylesheetParsingComplete
     */
    private boolean m_parsingComplete = false;
    private java.util.Vector m_prefixMappings = new java.util.Vector();

    ////////////////////////////////////////////////////////////////////
    // Implementation of the EntityResolver interface.
    ////////////////////////////////////////////////////////////////////
    /**
     * If we have a URL to a XML fragment, this is set
     * to false until the ID is found.
     * (warning: I worry that this should be in a stack).
     */
    private boolean m_shouldProcess = true;

    ////////////////////////////////////////////////////////////////////
    // Implementation of DTDHandler interface.
    ////////////////////////////////////////////////////////////////////
    /**
     * If we have a URL to a XML fragment, the value is stored
     * in this string, and the m_shouldProcess flag is set to
     * false until we match an ID with this string.
     * (warning: I worry that this should be in a stack).
     */
    private String m_fragmentIDString;
    /**
     * Keep track of the elementID, so we can tell when
     * is has completed.  This isn't a real ID, but rather
     * a nesting level.  However, it's good enough for
     * our purposes.
     * (warning: I worry that this should be in a stack).
     */
    private int m_elementID = 0;
    /**
     * The ID of the fragment that has been found
     * (warning: I worry that this should be in a stack).
     */
    private int m_fragmentID = 0;

    ////////////////////////////////////////////////////////////////////
    // Implementation of ContentHandler interface.
    ////////////////////////////////////////////////////////////////////
    /**
     * The XSLT TransformerFactory for needed services.
     */
    private TransformerFactoryImpl m_stylesheetProcessor;
    /**
     * The current stylesheet type.
     */
    private int m_stylesheetType = STYPE_ROOT;
    /**
     * The stack of stylesheets being processed.
     */
    private Stack m_stylesheets = new Stack();
    /**
     * The stack of current processors.
     */
    private Stack m_processors = new Stack();
    /**
     * The root of the XSLT Schema, which tells us how to
     * transition content handlers, create elements, etc.
     * For the moment at least, this can't be static, since
     * the processors store state.
     */
    private XSLTSchema m_schema = new XSLTSchema();
    /**
     * The stack of elements, pushed and popped as events occur.
     */
    private Stack m_elems = new Stack();
    /**
     * An increasing number that is used to indicate the order in which this element
     * was encountered during the parse of the XSLT tree.
     */
    private int m_docOrderCount = 0;
    /**
     * The top of this stack should contain the currently processed
     * stylesheet SAX locator object.
     */
    private Stack m_stylesheetLocatorStack = new Stack();
    /**
     * A stack of URL hrefs for imported stylesheets.  This is
     * used to diagnose circular imports.
     */
    private Stack m_importStack = new Stack();
    /**
     * A stack of Source objects obtained from a URIResolver,
     * for each element in this stack there is a 1-1 correspondence
     * with an element in the m_importStack.
     */
    private Stack m_importSourceStack = new Stack();
    /**
     * If this is set to true, we've already warned about using the
     * older XSLT namespace URL.
     */
    private boolean warnedAboutOldXSLTNamespace = false;
    /**
     * The originating node if the current stylesheet is being created
     * from a DOM.
     *
     * @see wx.xml.xalan.xml.utils.NodeConsumer
     */
    private Node m_originatingNode;
    /**
     * Stack of booleans that are pushed and popped in start/endElement depending
     * on the value of xml:space=default/preserve.
     */
    private BoolStack m_spacePreserveStack = new BoolStack();

    /**
     * Create a StylesheetHandler object, creating a root stylesheet
     * as the target.
     *
     * @param processor non-null reference to the transformer factory that owns this handler.
     * @throws TransformerConfigurationException if a StylesheetRoot
     *                                           can not be constructed for some reason.
     */
    public StylesheetHandler(TransformerFactoryImpl processor)
        throws TransformerConfigurationException {
        Class func = wx.xml.xalan.xalan.templates.FuncDocument.class;
        m_funcTable.installFunction("document", func);

        // func = new wx.xml.xalan.xalan.templates.FuncKey();
        // FunctionTable.installFunction("key", func);
        func = wx.xml.xalan.xalan.templates.FuncFormatNumb.class;

        m_funcTable.installFunction("format-number", func);

        m_optimize = ((Boolean) processor.getAttribute(
            TransformerFactoryImpl.FEATURE_OPTIMIZE)).booleanValue();
        m_incremental = ((Boolean) processor.getAttribute(
            TransformerFactoryImpl.FEATURE_INCREMENTAL)).booleanValue();
        m_source_location = ((Boolean) processor.getAttribute(
            TransformerFactoryImpl.FEATURE_SOURCE_LOCATION)).booleanValue();
        // m_schema = new XSLTSchema();
        init(processor);

    }

    /**
     * Do common initialization.
     *
     * @param processor non-null reference to the transformer factory that owns this handler.
     */
    void init(TransformerFactoryImpl processor) {
        m_stylesheetProcessor = processor;

        // Set the initial content handler.
        m_processors.push(m_schema.getElementProcessor());
        this.pushNewNamespaceSupport();

        // m_includeStack.push(SystemIDResolver.getAbsoluteURI(this.getBaseIdentifier(), null));
        // initXPath(processor, null);
    }

    /**
     * Process an expression string into an XPath.
     * Must be public for access by the AVT class.
     *
     * @param str A non-null reference to a valid or invalid XPath expression string.
     * @return A non-null reference to an XPath object that represents the string argument.
     * @throws javax.xml.transform.TransformerException if the expression can not be processed.
     * @see <a href="http://www.w3.org/TR/xslt#section-Expressions">Section 4 Expressions in XSLT Specification</a>
     */
    public XPath createXPath(String str, ElemTemplateElement owningTemplate)
        throws javax.xml.transform.TransformerException {
        ErrorListener handler = m_stylesheetProcessor.getErrorListener();
        XPath xpath = new XPath(str, owningTemplate, this, XPath.SELECT, handler,
            m_funcTable);
        // Visit the expression, registering namespaces for any extension functions it includes.
        xpath.callVisitors(xpath, new ExpressionVisitor(getStylesheetRoot()));
        return xpath;
    }

    /**
     * Process an expression string into an XPath.
     *
     * @param str A non-null reference to a valid or invalid match pattern string.
     * @return A non-null reference to an XPath object that represents the string argument.
     * @throws javax.xml.transform.TransformerException if the pattern can not be processed.
     * @see <a href="http://www.w3.org/TR/xslt#patterns">Section 5.2 Patterns in XSLT Specification</a>
     */
    XPath createMatchPatternXPath(String str, ElemTemplateElement owningTemplate)
        throws javax.xml.transform.TransformerException {
        ErrorListener handler = m_stylesheetProcessor.getErrorListener();
        XPath xpath = new XPath(str, owningTemplate, this, XPath.MATCH, handler,
            m_funcTable);
        // Visit the expression, registering namespaces for any extension functions it includes.
        xpath.callVisitors(xpath, new ExpressionVisitor(getStylesheetRoot()));
        return xpath;
    }

    /**
     * Given a namespace, get the corrisponding prefix from the current
     * namespace support context.
     *
     * @param prefix The prefix to look up, which may be an empty string ("") for the default Namespace.
     * @return The associated Namespace URI, or null if the prefix
     * is undeclared in this context.
     */
    public String getNamespaceForPrefix(String prefix) {
        return this.getNamespaceSupport().getURI(prefix);
    }

    /**
     * Given a namespace, get the corrisponding prefix.  This is here only
     * to support the {@link wx.xml.xalan.xml.utils.PrefixResolver} interface,
     * and will throw an error if invoked on this object.
     *
     * @param prefix  The prefix to look up, which may be an empty string ("") for the default Namespace.
     * @param context The node context from which to look up the URI.
     * @return The associated Namespace URI, or null if the prefix
     * is undeclared in this context.
     */
    public String getNamespaceForPrefix(String prefix, org.w3c.dom.Node context) {

        // Don't need to support this here.  Return the current URI for the prefix,
        // ignoring the context.
        assertion(true, "can't process a context node in StylesheetHandler!");

        return null;
    }

    /**
     * Utility function to see if the stack contains the given URL.
     *
     * @param stack non-null reference to a Stack.
     * @param url   URL string on which an equality test will be performed.
     * @return true if the stack contains the url argument.
     */
    private boolean stackContains(Stack stack, String url) {

        int     n        = stack.size();
        boolean contains = false;

        for (int i = 0; i < n; i++) {
            String url2 = (String) stack.elementAt(i);

            if (url2.equals(url)) {
                contains = true;

                break;
            }
        }

        return contains;
    }

    /**
     * When this object is used as a ContentHandler or ContentHandler, it will
     * create a Templates object, which the caller can get once
     * the SAX events have been completed.
     *
     * @return The stylesheet object that was created during
     * the SAX event process, or null if no stylesheet has
     * been created.
     * <p>
     * Author <a href="mailto:scott_boag@lotus.com">Scott Boag</a>
     */
    public Templates getTemplates() {
        return getStylesheetRoot();
    }

    /**
     * Get the base ID (URI or system ID) from where relative
     * URLs will be resolved.
     *
     * @return The systemID that was set with {@link #setSystemId}.
     */
    public String getSystemId() {
        return this.getBaseIdentifier();
    }

    /**
     * Set the base ID (URL or system ID) for the stylesheet
     * created by this builder.  This must be set in order to
     * resolve relative URLs in the stylesheet.
     *
     * @param baseID Base URL for this stylesheet.
     */
    public void setSystemId(String baseID) {
        pushBaseIndentifier(baseID);
    }

    /**
     * Resolve an external entity.
     *
     * @param publicId The public identifer, or null if none is
     *                 available.
     * @param systemId The system identifier provided in the XML
     *                 document.
     * @return The new input source, or null to require the
     * default behaviour.
     * @throws org.xml.sax.SAXException if the entity can not be resolved.
     */
    public InputSource resolveEntity(String publicId, String systemId)
        throws org.xml.sax.SAXException {
        return getCurrentProcessor().resolveEntity(this, publicId, systemId);
    }

    /**
     * Receive notification of a notation declaration.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass if they wish to keep track of the notations
     * declared in a document.</p>
     *
     * @param name     The notation name.
     * @param publicId The notation public identifier, or null if not
     *                 available.
     * @param systemId The notation system identifier.
     * @see org.xml.sax.DTDHandler#notationDecl
     */
    public void notationDecl(String name, String publicId, String systemId) {
        getCurrentProcessor().notationDecl(this, name, publicId, systemId);
    }

    /**
     * Receive notification of an unparsed entity declaration.
     *
     * @param name         The entity name.
     * @param publicId     The entity public identifier, or null if not
     *                     available.
     * @param systemId     The entity system identifier.
     * @param notationName The name of the associated notation.
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
     */
    public void unparsedEntityDecl(String name, String publicId,
                                   String systemId, String notationName) {
        getCurrentProcessor().unparsedEntityDecl(this, name, publicId, systemId,
            notationName);
    }

    /**
     * Given a namespace URI, and a local name or a node type, get the processor
     * for the element, or return null if not allowed.
     *
     * @param uri       The Namespace URI, or an empty string.
     * @param localName The local name (without prefix), or empty string if not namespace processing.
     * @param rawName   The qualified name (with prefix).
     * @return A non-null reference to a element processor.
     * @throws org.xml.sax.SAXException if the element is not allowed in the
     *                                  found position in the stylesheet.
     */
    XSLTElementProcessor getProcessorFor(
        String uri, String localName, String rawName)
        throws org.xml.sax.SAXException {

        XSLTElementProcessor currentProcessor = getCurrentProcessor();
        XSLTElementDef       def              = currentProcessor.getElemDef();
        XSLTElementProcessor elemProcessor    = def.getProcessorFor(uri, localName);

        if (null == elemProcessor
            && !(currentProcessor instanceof ProcessorStylesheetDoc)
            && ((null == getStylesheet()
            || Double.valueOf(getStylesheet().getVersion()).doubleValue()
            > Constants.XSLTVERSUPPORTED)
            || (!uri.equals(Constants.S_XSLNAMESPACEURL) &&
            currentProcessor instanceof ProcessorStylesheetElement)
            || getElemVersion() > Constants.XSLTVERSUPPORTED
        )) {
            elemProcessor = def.getProcessorForUnknown(uri, localName);
        }

        if (null == elemProcessor)
            error(XSLMessages.createMessage(XSLTErrorResources.ER_NOT_ALLOWED_IN_POSITION, new Object[]{rawName}), null);//rawName + " is not allowed in this position in the stylesheet!",


        return elemProcessor;
    }

    /**
     * Receive a Locator object for document events.
     * This is called by the parser to push a locator for the
     * stylesheet being parsed. The stack needs to be popped
     * after the stylesheet has been parsed. We pop in
     * popStylesheet.
     *
     * @param locator A locator for all SAX document events.
     * @see org.xml.sax.ContentHandler#setDocumentLocator
     * @see org.xml.sax.Locator
     */
    public void setDocumentLocator(Locator locator) {

        // System.out.println("pushing locator for: "+locator.getSystemId());
        m_stylesheetLocatorStack.push(new SAXSourceLocator(locator));
    }

    /**
     * Receive notification of the beginning of the document.
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#startDocument
     */
    public void startDocument() throws org.xml.sax.SAXException {
        m_stylesheetLevel++;
        pushSpaceHandling(false);
    }

    /**
     * Test whether the _last_ endDocument() has been processed.
     * This is needed as guidance for stylesheet optimization
     * and compilation engines, which generally don't want to start
     * until all included and imported stylesheets have been fully
     * parsed.
     *
     * @return true iff the complete stylesheet tree has been built.
     */
    public boolean isStylesheetParsingComplete() {
        return m_parsingComplete;
    }

    /**
     * Receive notification of the end of the document.
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#endDocument
     */
    public void endDocument() throws org.xml.sax.SAXException {

        try {
            if (null != getStylesheetRoot()) {
                if (0 == m_stylesheetLevel)
                    getStylesheetRoot().recompose();
            } else
                throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_STYLESHEETROOT, null)); //"Did not find the stylesheet root!");

            XSLTElementProcessor elemProcessor = getCurrentProcessor();

            if (null != elemProcessor)
                elemProcessor.startNonText(this);

            m_stylesheetLevel--;

            popSpaceHandling();

            // WARNING: This test works only as long as stylesheets are parsed
            // more or less recursively. If we switch to an iterative "work-list"
            // model, this will become true prematurely. In that case,
            // isStylesheetParsingComplete() will have to be adjusted to be aware
            // of the worklist.
            m_parsingComplete = (m_stylesheetLevel < 0);
        } catch (TransformerException te) {
            throw new org.xml.sax.SAXException(te);
        }
    }

    /**
     * Receive notification of the start of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the start of
     * each element (such as allocating a new tree node or writing
     * output to a file).</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @param uri    The Namespace URI mapped to the prefix.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#startPrefixMapping
     */
    public void startPrefixMapping(String prefix, String uri)
        throws org.xml.sax.SAXException {

        // m_nsSupport.pushContext();
        // this.getNamespaceSupport().declarePrefix(prefix, uri);
        //m_prefixMappings.add(prefix); // JDK 1.2+ only -sc
        //m_prefixMappings.add(uri); // JDK 1.2+ only -sc
        m_prefixMappings.addElement(prefix); // JDK 1.1.x compat -sc
        m_prefixMappings.addElement(uri); // JDK 1.1.x compat -sc
    }

    /**
     * Receive notification of the end of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the start of
     * each element (such as allocating a new tree node or writing
     * output to a file).</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#endPrefixMapping
     */
    public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException {

        // m_nsSupport.popContext();
    }

    /**
     * Flush the characters buffer.
     *
     * @throws org.xml.sax.SAXException
     */
    private void flushCharacters() throws org.xml.sax.SAXException {

        XSLTElementProcessor elemProcessor = getCurrentProcessor();

        if (null != elemProcessor)
            elemProcessor.startNonText(this);
    }

    /**
     * Receive notification of the start of an element.
     *
     * @param uri        The Namespace URI, or an empty string.
     * @param localName  The local name (without prefix), or empty string if not namespace processing.
     * @param rawName    The qualified name (with prefix).
     * @param attributes The specified or defaulted attributes.
     * @throws org.xml.sax.SAXException
     */
    public void startElement(
        String uri, String localName, String rawName, Attributes attributes)
        throws org.xml.sax.SAXException {
        NamespaceSupport nssupport = this.getNamespaceSupport();
        nssupport.pushContext();

        int n = m_prefixMappings.size();

        for (int i = 0; i < n; i++) {
            String prefix = (String) m_prefixMappings.elementAt(i++);
            String nsURI  = (String) m_prefixMappings.elementAt(i);
            nssupport.declarePrefix(prefix, nsURI);
        }
        //m_prefixMappings.clear(); // JDK 1.2+ only -sc
        m_prefixMappings.removeAllElements(); // JDK 1.1.x compat -sc

        m_elementID++;

        // This check is currently done for all elements.  We should possibly consider
        // limiting this check to xsl:stylesheet elements only since that is all it really
        // applies to.  Also, it could be bypassed if m_shouldProcess is already true.
        // In other words, the next two statements could instead look something like this:
        // if (!m_shouldProcess)
        // {
        //   if (localName.equals(Constants.ELEMNAME_STYLESHEET_STRING) &&
        //       url.equals(Constants.S_XSLNAMESPACEURL))
        //   {
        //     checkForFragmentID(attributes);
        //     if (!m_shouldProcess)
        //       return;
        //   }
        //   else
        //     return;
        // }
        // I didn't include this code statement at this time because in practice
        // it is a small performance hit and I was waiting to see if its absence
        // caused a problem. - GLP

        checkForFragmentID(attributes);

        if (!m_shouldProcess)
            return;

        flushCharacters();

        pushSpaceHandling(attributes);

        XSLTElementProcessor elemProcessor = getProcessorFor(uri, localName,
            rawName);

        if (null != elemProcessor)  // defensive, for better multiple error reporting. -sb
        {
            this.pushProcessor(elemProcessor);
            elemProcessor.startElement(this, uri, localName, rawName, attributes);
        } else {
            m_shouldProcess = false;
            popSpaceHandling();
        }

    }

    /**
     * Receive notification of the end of an element.
     *
     * @param uri       The Namespace URI, or an empty string.
     * @param localName The local name (without prefix), or empty string if not namespace processing.
     * @param rawName   The qualified name (with prefix).
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#endElement
     */
    public void endElement(String uri, String localName, String rawName)
        throws org.xml.sax.SAXException {

        m_elementID--;

        if (!m_shouldProcess)
            return;

        if ((m_elementID + 1) == m_fragmentID)
            m_shouldProcess = false;

        flushCharacters();

        popSpaceHandling();

        XSLTElementProcessor p = getCurrentProcessor();

        p.endElement(this, uri, localName, rawName);
        this.popProcessor();
        this.getNamespaceSupport().popContext();
    }

    /**
     * Receive notification of character data inside an element.
     *
     * @param ch     The characters.
     * @param start  The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#characters
     */
    public void characters(char ch[], int start, int length)
        throws org.xml.sax.SAXException {

        if (!m_shouldProcess)
            return;

        XSLTElementProcessor elemProcessor = getCurrentProcessor();
        XSLTElementDef       def           = elemProcessor.getElemDef();

        if (def.getType() != XSLTElementDef.T_PCDATA)
            elemProcessor = def.getProcessorFor(null, "text()");

        if (null == elemProcessor) {

            // If it's whitespace, just ignore it, otherwise flag an error.
            if (!XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
                error(
                    XSLMessages.createMessage(XSLTErrorResources.ER_NONWHITESPACE_NOT_ALLOWED_IN_POSITION, null), null);//"Non-whitespace text is not allowed in this position in the stylesheet!",

        } else
            elemProcessor.characters(this, ch, start, length);
    }

    /**
     * Receive notification of ignorable whitespace in element content.
     *
     * @param ch     The whitespace characters.
     * @param start  The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#ignorableWhitespace
     */
    public void ignorableWhitespace(char ch[], int start, int length)
        throws org.xml.sax.SAXException {

        if (!m_shouldProcess)
            return;

        getCurrentProcessor().ignorableWhitespace(this, ch, start, length);
    }

    /**
     * Receive notification of a processing instruction.
     *
     * <p>The Parser will invoke this method once for each processing
     * instruction found: note that processing instructions may occur
     * before or after the main document element.</p>
     *
     * <p>A SAX parser should never report an XML declaration (XML 1.0,
     * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
     * using this method.</p>
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param target The processing instruction target.
     * @param data   The processing instruction data, or null if
     *               none is supplied.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    public void processingInstruction(String target, String data)
        throws org.xml.sax.SAXException {
        if (!m_shouldProcess)
            return;

        // Recreating Scott's kluge:
        // A xsl:for-each or xsl:apply-templates may have a special
        // PI that tells us not to cache the document.  This PI
        // should really be namespaced.
        //    String localName = getLocalName(target);
        //    String ns = m_stylesheet.getNamespaceFromStack(target);
        //
        // %REVIEW%: We need a better PI architecture

        String prefix = "", ns = "", localName = target;
        int    colon  = target.indexOf(':');
        if (colon >= 0) {
            ns = getNamespaceForPrefix(prefix = target.substring(0, colon));
            localName = target.substring(colon + 1);
        }

        try {
            // A xsl:for-each or xsl:apply-templates may have a special
            // PI that tells us not to cache the document.  This PI
            // should really be namespaced... but since the XML Namespaces
            // spec never defined namespaces as applying to PI's, and since
            // the testcase we're trying to support is inconsistant in whether
            // it binds the prefix, I'm going to make this sloppy for
            // testing purposes.
            if (
                "xalan-doc-cache-off".equals(target) ||
                    "xalan:doc-cache-off".equals(target) ||
                    ("doc-cache-off".equals(localName) &&
                        ns.equals("wx.xml.xalan.xalan.xslt.extensions.Redirect"))
                ) {
                if (!(m_elems.peek() instanceof ElemForEach))
                    throw new TransformerException
                        ("xalan:doc-cache-off not allowed here!",
                            getLocator());
                ElemForEach elem = (ElemForEach) m_elems.peek();

                elem.m_doc_cache_off = true;

                //System.out.println("JJK***** Recognized <? {"+ns+"}"+prefix+":"+localName+" "+data+"?>");
            }
        } catch (Exception e) {
            // JJK: Officially, unknown PIs can just be ignored.
            // Do we want to issue a warning?
        }


        flushCharacters();
        getCurrentProcessor().processingInstruction(this, target, data);
    }

    /**
     * Receive notification of a skipped entity.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param name The name of the skipped entity.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    public void skippedEntity(String name) throws org.xml.sax.SAXException {

        if (!m_shouldProcess)
            return;

        getCurrentProcessor().skippedEntity(this, name);
    }

    /**
     * Warn the user of an problem.
     *
     * @param msg  An key into the {@link wx.xml.xalan.xalan.res.XSLTErrorResources}
     *             table, that is one of the WG_ prefixed definitions.
     * @param args An array of arguments for the given warning.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#warning}
     *                                  method chooses to flag this condition as an error.
     * @xsl.usage internal
     */
    public void warn(String msg, Object args[]) throws org.xml.sax.SAXException {

        String           formattedMsg = XSLMessages.createWarning(msg, args);
        SAXSourceLocator locator      = getLocator();
        ErrorListener    handler      = m_stylesheetProcessor.getErrorListener();

        try {
            if (null != handler)
                handler.warning(new TransformerException(formattedMsg, locator));
        } catch (TransformerException te) {
            throw new org.xml.sax.SAXException(te);
        }
    }

    /**
     * Assert that a condition is true.  If it is not true, throw an error.
     *
     * @param condition false if an error should not be thrown, otherwise true.
     * @param msg       Error message to be passed to the RuntimeException as an
     *                  argument.
     * @throws RuntimeException if the condition is not true.
     * @xsl.usage internal
     */
    private void assertion(boolean condition, String msg) throws RuntimeException {
        if (!condition)
            throw new RuntimeException(msg);
    }

    /**
     * Tell the user of an error, and probably throw an
     * exception.
     *
     * @param msg An error message.
     * @param e   An error which the SAXException should wrap.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#error}
     *                                  method chooses to flag this condition as an error.
     * @xsl.usage internal
     */
    protected void error(String msg, Exception e)
        throws org.xml.sax.SAXException {

        SAXSourceLocator     locator = getLocator();
        ErrorListener        handler = m_stylesheetProcessor.getErrorListener();
        TransformerException pe;

        if (!(e instanceof TransformerException)) {
            pe = (null == e)
                 ? new TransformerException(msg, locator)
                 : new TransformerException(msg, locator, e);
        } else
            pe = (TransformerException) e;

        if (null != handler) {
            try {
                handler.error(pe);
            } catch (TransformerException te) {
                throw new org.xml.sax.SAXException(te);
            }
        } else
            throw new org.xml.sax.SAXException(pe);
    }

    /**
     * Tell the user of an error, and probably throw an
     * exception.
     *
     * @param msg  A key into the {@link wx.xml.xalan.xalan.res.XSLTErrorResources}
     *             table, that is one of the WG_ prefixed definitions.
     * @param args An array of arguments for the given warning.
     * @param e    An error which the SAXException should wrap.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#error}
     *                                  method chooses to flag this condition as an error.
     * @xsl.usage internal
     */
    protected void error(String msg, Object args[], Exception e)
        throws org.xml.sax.SAXException {

        String formattedMsg = XSLMessages.createMessage(msg, args);

        error(formattedMsg, e);
    }

    /**
     * Receive notification of a XSLT processing warning.
     *
     * @param e The warning information encoded as an exception.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#warning}
     *                                  method chooses to flag this condition as an error.
     */
    public void warning(org.xml.sax.SAXParseException e)
        throws org.xml.sax.SAXException {

        String           formattedMsg = e.getMessage();
        SAXSourceLocator locator      = getLocator();
        ErrorListener    handler      = m_stylesheetProcessor.getErrorListener();

        try {
            handler.warning(new TransformerException(formattedMsg, locator));
        } catch (TransformerException te) {
            throw new org.xml.sax.SAXException(te);
        }
    }

    /**
     * Receive notification of a recoverable XSLT processing error.
     *
     * @param e The error information encoded as an exception.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#error}
     *                                  method chooses to flag this condition as an error.
     */
    public void error(org.xml.sax.SAXParseException e)
        throws org.xml.sax.SAXException {

        String           formattedMsg = e.getMessage();
        SAXSourceLocator locator      = getLocator();
        ErrorListener    handler      = m_stylesheetProcessor.getErrorListener();

        try {
            handler.error(new TransformerException(formattedMsg, locator));
        } catch (TransformerException te) {
            throw new org.xml.sax.SAXException(te);
        }
    }

    /**
     * Report a fatal XSLT processing error.
     *
     * @param e The error information encoded as an exception.
     * @throws org.xml.sax.SAXException that wraps a
     *                                  {@link javax.xml.transform.TransformerException} if the current
     *                                  {@link javax.xml.transform.ErrorListener#fatalError}
     *                                  method chooses to flag this condition as an error.
     */
    public void fatalError(org.xml.sax.SAXParseException e)
        throws org.xml.sax.SAXException {

        String           formattedMsg = e.getMessage();
        SAXSourceLocator locator      = getLocator();
        ErrorListener    handler      = m_stylesheetProcessor.getErrorListener();

        try {
            handler.fatalError(new TransformerException(formattedMsg, locator));
        } catch (TransformerException te) {
            throw new org.xml.sax.SAXException(te);
        }
    }

    /**
     * Check to see if an ID attribute matched the #id, called
     * from startElement.
     *
     * @param attributes The specified or defaulted attributes.
     */
    private void checkForFragmentID(Attributes attributes) {

        if (!m_shouldProcess) {
            if ((null != attributes) && (null != m_fragmentIDString)) {
                int n = attributes.getLength();

                for (int i = 0; i < n; i++) {
                    String name = attributes.getQName(i);

                    if (name.equals(Constants.ATTRNAME_ID)) {
                        String val = attributes.getValue(i);

                        if (val.equalsIgnoreCase(m_fragmentIDString)) {
                            m_shouldProcess = true;
                            m_fragmentID = m_elementID;
                        }
                    }
                }
            }
        }
    }

    /**
     * Get the XSLT TransformerFactoryImpl for needed services.
     * TODO: This method should be renamed.
     *
     * @return The TransformerFactoryImpl that owns this handler.
     */
    public TransformerFactoryImpl getStylesheetProcessor() {
        return m_stylesheetProcessor;
    }

    /**
     * Get the type of stylesheet that should be built
     * or is being processed.
     *
     * @return one of STYPE_ROOT, STYPE_INCLUDE, or STYPE_IMPORT.
     */
    int getStylesheetType() {
        return m_stylesheetType;
    }

    /**
     * Set the type of stylesheet that should be built
     * or is being processed.
     *
     * @param type Must be one of STYPE_ROOT, STYPE_INCLUDE, or STYPE_IMPORT.
     */
    void setStylesheetType(int type) {
        m_stylesheetType = type;
    }

    /**
     * Return the stylesheet that this handler is constructing.
     *
     * @return The current stylesheet that is on top of the stylesheets stack,
     * or null if no stylesheet is on the stylesheets stack.
     */
    Stylesheet getStylesheet() {
        return (m_stylesheets.size() == 0)
               ? null : (Stylesheet) m_stylesheets.peek();
    }

    /**
     * Return the last stylesheet that was popped off the stylesheets stack.
     *
     * @return The last popped stylesheet, or null.
     */
    Stylesheet getLastPoppedStylesheet() {
        return m_lastPoppedStylesheet;
    }

    /**
     * Return the stylesheet root that this handler is constructing.
     *
     * @return The root stylesheet of the stylesheets tree.
     */
    public StylesheetRoot getStylesheetRoot() {
        if (m_stylesheetRoot != null) {
            m_stylesheetRoot.setOptimizer(m_optimize);
            m_stylesheetRoot.setIncremental(m_incremental);
            m_stylesheetRoot.setSource_location(m_source_location);
        }
        return m_stylesheetRoot;
    }

    /**
     * Push the current stylesheet being constructed. If no other stylesheets
     * have been pushed onto the stack, assume the argument is a stylesheet
     * root, and also set the stylesheet root member.
     *
     * @param s non-null reference to a stylesheet.
     */
    public void pushStylesheet(Stylesheet s) {

        if (m_stylesheets.size() == 0)
            m_stylesheetRoot = (StylesheetRoot) s;

        m_stylesheets.push(s);
    }

    /**
     * Pop the last stylesheet pushed, and return the stylesheet that this
     * handler is constructing, and set the last popped stylesheet member.
     * Also pop the stylesheet locator stack.
     *
     * @return The stylesheet popped off the stack, or the last popped stylesheet.
     */
    Stylesheet popStylesheet() {

        // The stylesheetLocatorStack needs to be popped because
        // a locator was pushed in for this stylesheet by the SAXparser by calling
        // setDocumentLocator().
        if (!m_stylesheetLocatorStack.isEmpty())
            m_stylesheetLocatorStack.pop();

        if (!m_stylesheets.isEmpty())
            m_lastPoppedStylesheet = (Stylesheet) m_stylesheets.pop();

        // Shouldn't this be null if stylesheets is empty?  -sb
        return m_lastPoppedStylesheet;
    }

    /**
     * Get the current XSLTElementProcessor at the top of the stack.
     *
     * @return Valid XSLTElementProcessor, which should never be null.
     */
    XSLTElementProcessor getCurrentProcessor() {
        return (XSLTElementProcessor) m_processors.peek();
    }

    /**
     * Push the current XSLTElementProcessor onto the top of the stack.
     *
     * @param processor non-null reference to the current element processor.
     */
    void pushProcessor(XSLTElementProcessor processor) {
        m_processors.push(processor);
    }

    /**
     * Pop the current XSLTElementProcessor from the top of the stack.
     *
     * @return the XSLTElementProcessor which was popped.
     */
    XSLTElementProcessor popProcessor() {
        return (XSLTElementProcessor) m_processors.pop();
    }

    /**
     * Get the root of the XSLT Schema, which tells us how to
     * transition content handlers, create elements, etc.
     *
     * @return The root XSLT Schema, which should never be null.
     * @xsl.usage internal
     */
    public XSLTSchema getSchema() {
        return m_schema;
    }

    /**
     * Get the current ElemTemplateElement at the top of the stack.
     *
     * @return Valid ElemTemplateElement, which may be null.
     */
    ElemTemplateElement getElemTemplateElement() {

        try {
            return (ElemTemplateElement) m_elems.peek();
        } catch (java.util.EmptyStackException ese) {
            return null;
        }
    }

    /**
     * Returns the next m_docOrderCount number and increments the number for future use.
     */
    int nextUid() {
        return m_docOrderCount++;
    }

    /**
     * Push the current XSLTElementProcessor to the top of the stack.  As a
     * side-effect, set the document order index (simply because this is a
     * convenient place to set it).
     *
     * @param elem Should be a non-null reference to the intended current
     *             template element.
     */
    void pushElemTemplateElement(ElemTemplateElement elem) {

        if (elem.getUid() == -1)
            elem.setUid(nextUid());

        m_elems.push(elem);
    }

    /**
     * Get the current XSLTElementProcessor from the top of the stack.
     *
     * @return the ElemTemplateElement which was popped.
     */
    ElemTemplateElement popElemTemplateElement() {
        return (ElemTemplateElement) m_elems.pop();
    }

    /**
     * Push a base identifier onto the base URI stack.
     *
     * @param baseID The current base identifier for this position in the
     *               stylesheet, which may be a fragment identifier, or which may be null.
     * @see <a href="http://www.w3.org/TR/xslt#base-uri">
     * Section 3.2 Base URI of XSLT specification.</a>
     */
    void pushBaseIndentifier(String baseID) {

        if (null != baseID) {
            int posOfHash = baseID.indexOf('#');

            if (posOfHash > -1) {
                m_fragmentIDString = baseID.substring(posOfHash + 1);
                m_shouldProcess = false;
            } else
                m_shouldProcess = true;
        } else
            m_shouldProcess = true;

        m_baseIdentifiers.push(baseID);
    }

    /**
     * Pop a base URI from the stack.
     *
     * @return baseIdentifier.
     */
    String popBaseIndentifier() {
        return (String) m_baseIdentifiers.pop();
    }

    /**
     * Return the base identifier.
     *
     * @return The base identifier of the current stylesheet.
     */
    public String getBaseIdentifier() {

        // Try to get the baseIdentifier from the baseIdentifier's stack,
        // which may not be the same thing as the value found in the
        // SourceLocators stack.
        String base = (String) (m_baseIdentifiers.isEmpty()
                                ? null : m_baseIdentifiers.peek());

        // Otherwise try the stylesheet.
        if (null == base) {
            SourceLocator locator = getLocator();

            base = (null == locator) ? "" : locator.getSystemId();
        }

        return base;
    }

    /**
     * Get the current stylesheet Locator object.
     *
     * @return non-null reference to the current locator object.
     */
    public SAXSourceLocator getLocator() {

        if (m_stylesheetLocatorStack.isEmpty()) {
            SAXSourceLocator locator = new SAXSourceLocator();

            locator.setSystemId(this.getStylesheetProcessor().getDOMsystemID());

            return locator;

            // m_stylesheetLocatorStack.push(locator);
        }

        return ((SAXSourceLocator) m_stylesheetLocatorStack.peek());
    }

    /**
     * Push an import href onto the stylesheet stack.
     *
     * @param hrefUrl non-null reference to the URL for the current imported
     *                stylesheet.
     */
    void pushImportURL(String hrefUrl) {
        m_importStack.push(hrefUrl);
    }

    /**
     * Push the Source of an import href onto the stylesheet stack,
     * obtained from a URIResolver, null if there is no URIResolver,
     * or if that resolver returned null.
     */
    void pushImportSource(Source sourceFromURIResolver) {
        m_importSourceStack.push(sourceFromURIResolver);
    }

    /**
     * See if the imported stylesheet stack already contains
     * the given URL.  Used to test for recursive imports.
     *
     * @param hrefUrl non-null reference to a URL string.
     * @return true if the URL is on the import stack.
     */
    boolean importStackContains(String hrefUrl) {
        return stackContains(m_importStack, hrefUrl);
    }

    /**
     * Pop an import href from the stylesheet stack.
     *
     * @return non-null reference to the import URL that was popped.
     */
    String popImportURL() {
        return (String) m_importStack.pop();
    }

    String peekImportURL() {
        return (String) m_importStack.peek();
    }

    Source peekSourceFromURIResolver() {
        return (Source) m_importSourceStack.peek();
    }

    /**
     * Pop a Source from a user provided URIResolver, corresponding
     * to the URL popped from the m_importStack.
     */
    Source popImportSource() {
        return (Source) m_importSourceStack.pop();
    }

    /**
     * Push a new NamespaceSupport instance.
     */
    void pushNewNamespaceSupport() {
        m_nsSupportStack.push(new NamespaceSupport2());
    }

    /**
     * Pop the current NamespaceSupport object.
     */
    void popNamespaceSupport() {
        m_nsSupportStack.pop();
    }

    /**
     * Get the current NamespaceSupport object.
     *
     * @return a non-null reference to the current NamespaceSupport object,
     * which is the top of the namespace support stack.
     */
    NamespaceSupport getNamespaceSupport() {
        return (NamespaceSupport) m_nsSupportStack.peek();
    }

    /**
     * Set the node that is originating the SAX event.
     *
     * @return Reference to node that originated the current event.
     * @see wx.xml.xalan.xml.utils.NodeConsumer
     */
    public Node getOriginatingNode() {
        return m_originatingNode;
    }

    /**
     * Set the node that is originating the SAX event.
     *
     * @param n Reference to node that originated the current event.
     * @see wx.xml.xalan.xml.utils.NodeConsumer
     */
    public void setOriginatingNode(Node n) {
        m_originatingNode = n;
    }

    /**
     * Return boolean value from the spacePreserve stack depending on the value
     * of xml:space=default/preserve.
     *
     * @return true if space should be preserved, false otherwise.
     */
    boolean isSpacePreserve() {
        return m_spacePreserveStack.peek();
    }

    /**
     * Pop boolean value from the spacePreserve stack.
     */
    void popSpaceHandling() {
        m_spacePreserveStack.pop();
    }

    /**
     * Push boolean value on to the spacePreserve stack.
     *
     * @param b true if space should be preserved, false otherwise.
     */
    void pushSpaceHandling(boolean b)
        throws org.xml.sax.SAXParseException {
        m_spacePreserveStack.push(b);
    }

    /**
     * Push boolean value on to the spacePreserve stack depending on the value
     * of xml:space=default/preserve.
     *
     * @param attrs list of attributes that were passed to startElement.
     */
    void pushSpaceHandling(Attributes attrs)
        throws org.xml.sax.SAXParseException {
        String value = attrs.getValue("xml:space");
        if (null == value) {
            m_spacePreserveStack.push(m_spacePreserveStack.peekOrFalse());
        } else if (value.equals("preserve")) {
            m_spacePreserveStack.push(true);
        } else if (value.equals("default")) {
            m_spacePreserveStack.push(false);
        } else {
            SAXSourceLocator locator = getLocator();
            ErrorListener    handler = m_stylesheetProcessor.getErrorListener();

            try {
                handler.error(new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_ILLEGAL_XMLSPACE_VALUE, null), locator)); //"Illegal value for xml:space", locator));
            } catch (TransformerException te) {
                throw new org.xml.sax.SAXParseException(te.getMessage(), locator, te);
            }
            m_spacePreserveStack.push(m_spacePreserveStack.peek());
        }
    }

    private double getElemVersion() {
        ElemTemplateElement elem    = getElemTemplateElement();
        double              version = -1;
        while ((version == -1 || version == Constants.XSLTVERSUPPORTED) && elem != null) {
            try {
                version = Double.valueOf(elem.getXmlVersion()).doubleValue();
            } catch (Exception ex) {
                version = -1;
            }
            elem = elem.getParentElem();
        }
        return (version == -1) ? Constants.XSLTVERSUPPORTED : version;
    }

    /**
     * @see PrefixResolver#handlesNullPrefixes()
     */
    public boolean handlesNullPrefixes() {
        return false;
    }

    /**
     * @return Optimization flag
     */
    public boolean getOptimize() {
        return m_optimize;
    }

    /**
     * @return Incremental flag
     */
    public boolean getIncremental() {
        return m_incremental;
    }

    /**
     * @return Source Location flag
     */
    public boolean getSource_location() {
        return m_source_location;
    }

}



